﻿namespace MicroCloud.Utils
{
    /// <summary>
    /// 32位雪花ID生成器（改写雪花算法生成规则）：
    /// 唯一ID支持int类型，阉割分布机器码段且降低并发量(时间戳采用分钟数)。
    /// 原雪花算法规则：正数位(1bit)：一个符号位，永远是0。时间戳(41bit) ：毫秒数，能撑139年。Worker ID：工作机器Id ( 10 bit, 最大值1023)。自增序列(12bit，最大值4096)：毫秒之内的自增，过了一毫秒会重新置0。生成结果为64bit的long类型数字，长度是18-19位。
    /// 改写后规则：正数位(1bit)：一个符号位，永远是0。时间戳(24bit) ：分钟数。自增序列(7bit，最大值128)：分钟之内的自增，过了一分钟会重新置0。生成结果为32bit的int类型数字，长度是11位以内。
    /// </summary>
    public class SnowflakeIdGeneratorSimple
    {
        #region "属性"
        #region "获取或设置 开始时间戳 (分钟，默认：2020-01-01)，这是一个避免重复的随机量，自行设定不要大于当前时间戳"
        private static int _Twepoch = 26296800;    // 1577808000000L/1000/60;26296800
        /// <summary>
        /// 获取或设置 开始时间戳 (分钟，默认：默认：2020-01-01)
        /// ，这是一个避免重复的随机量，自行设定不要大于当前时间戳
        /// </summary>
        public static int Twepoch
        {
            get { return _Twepoch; }
            set
            {
                var twepoch = value;
                int timestamp = TimeGen();
                while (twepoch > timestamp)
                {
                    throw new System.Exception($"自行设定不要大于当前时间戳 {timestamp} (总分钟数)");
                }
                _Twepoch = twepoch;
            }
        }
        #endregion
        #region "获取 一微秒内可以产生计数，如果达到该值则等到下一分钟在进行生成"
        /// <summary>
        /// 获取 一微秒内可以产生计数，如果达到该值则等到下一分钟在进行生成
        /// (127)
        /// </summary>
        public static int SequenceMask { get { return -1 ^ -1 << SequenceBits; } }
        #endregion

        #region "获取 计数器字节数，7个字节用来保存计数码"
        /// <summary>
        /// 获取 计数器字节数，7个字节用来保存计数码
        /// </summary>
        private static int SequenceBits => 7;
        #endregion
        #region "获取 时间戳左移动位数就是机器码和计数器总字节数"
        /// <summary>
        /// 获取 时间戳左移动位数就是机器码和计数器总字节数
        /// (时间截向左移7位：计数器字节数)
        /// </summary>
        private static int TimestampLeftShift { get { return SequenceBits; } }
        #endregion
        #region "获取或设置 最后时间戳(分钟)"
        /// <summary>
        /// 获取或设置 最后时间戳(分钟)
        /// </summary>
        private static int LastTimestamp { get; set; } = -1;
        #endregion
        #region "获取或设置 序列计数器"
        /// <summary>
        /// 获取或设置 序列计数器
        /// (0~127)
        /// </summary>
        private static int Sequence { get; set; } = 0;
        #endregion

        #region "获取或设置 线程锁对象"
        /// <summary>
        /// 获取或设置 线程锁对象
        /// </summary>
        private static object Locker { get; set; } = new object();
        #endregion

        #endregion

        #region "构造函数-私有"
        #region "初始化一个32位雪花ID生成器的新实例"
        /// <summary>
        /// 初始化一个32位雪花ID生成器的新实例
        /// </summary>
        private SnowflakeIdGeneratorSimple()
        { }
        #endregion

        #endregion

        #region "方法"
        #region "生成一个新的32位雪花ID"
        /// <summary>
        /// 生成一个新的32位雪花ID
        /// </summary>
        /// <returns>32位int类型雪花ID</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public static int NewId()
        {
            //异步情况下，使用Lock确保产生唯一ID
            lock (Locker)
            {
                int timestamp = TimeGen();
                if (LastTimestamp == timestamp)
                {
                    //同一分钟中生成ID
                    Sequence = Sequence + 1 & SequenceMask; //用&运算计算该分钟内产生的计数是否已经到达上限
                    if (Sequence == 0)
                    {
                        //一分钟内产生的ID计数已达上限，等待下一分钟
                        timestamp = TillNextMinutes(LastTimestamp);
                    }
                }
                else
                {
                    //不同分钟生成ID
                    Sequence = 0; //计数清0
                }
                if (timestamp < LastTimestamp)
                {
                    //如果当前时间戳比上一次生成ID时时间戳还小，抛出异常，因为不能保证现在生成的ID之前没有生成过
                    //throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds",
                    //    LastTimestamp - timestamp));
                    throw new System.Exception($"时钟向后移动。拒绝生成id {LastTimestamp - timestamp} 分钟");
                }
                LastTimestamp = timestamp; //把当前时间戳保存为最后生成ID的时间戳
                int newId = timestamp - Twepoch << TimestampLeftShift | Sequence;   // 移位并通过或运算拼到一起组成32位的ID
                return newId;
            }
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "获取下一分钟时间戳"
        /// <summary>
        /// 获取下一分钟时间戳
        /// </summary>
        /// <param name="lastTimestamp">最后时间戳</param>
        /// <returns></returns>
        private static int TillNextMinutes(int lastTimestamp)
        {
            int timestamp = TimeGen();
            while (timestamp <= lastTimestamp)
            {
                timestamp = TimeGen();
            }
            return timestamp;
        }
        #endregion
        #region "生成当前时间戳(分钟)"
        /// <summary>
        /// 生成当前时间戳(分钟)
        /// </summary>
        /// <returns>当前时间(总分钟数)</returns>
        private static int TimeGen()
        {
            return (int)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMinutes;
        }
        #endregion

        #endregion

    }

}